#include <stdlib.h>
#include <assert.h>
#include "priority_queue.h"

#define NULL_CHECK(var,rtn_val) if(var == NULL){ return rtn_val;}

/*
	______     _            _ _           _____
	| ___ \   (_)          (_) |         |  _  |
	| |_/ / __ _  ___  _ __ _| |_ _   _  | | | |_   _  ___ _   _  ___
	|  __/ '__| |/ _ \| '__| | __| | | | | | | | | | |/ _ \ | | |/ _ \
	| |  | |  | | (_) | |  | | |_| |_| | \ \/' / |_| |  __/ |_| |  __/
	\_|  |_|  |_|\___/|_|  |_|\__|\__, |  \_/\_\\__,_|\___|\__,_|\___|
								   __/ |
	   							  |___/
*/


typedef struct Node_t {
	PriorityQueueElement data;
	Priority priority;
	struct Node_t *next;
} *Node;

struct PriorityQueue_t
{
	Node first;
	CopyPriorityQueueElement copyFunction;
	FreePriorityQueueElement freeFunction;
};

/*
			_       _                 ___                 _   _
  /\  /\___| |_ __ (_)_ __   __ _    / __\   _ _ __   ___| |_(_) ___  _ __  ___
 / /_/ / _ \ | '_ \| | '_ \ / _` |  / _\| | | | '_ \ / __| __| |/ _ \| '_ \/ __|
/ __  /  __/ | |_) | | | | | (_| | / /  | |_| | | | | (__| |_| | (_) | | | \__ \
\/ /_/ \___|_| .__/|_|_| |_|\__, | \/    \__,_|_| |_|\___|\__|_|\___/|_| |_|___/
		     |_|            |___/
*/

#define FOR_EACH(node,pq) for(Node node=pq->first;node;node=node->next)

//filter that passes everything
static bool passEverything(PriorityQueueElement element) {
	return true;
}


/*
  _____       _             __                    ___                 _   _
  \_   \_ __ | |_ ___ _ __ / _| __ _  ___ ___    / __\   _ _ __   ___| |_(_) ___  _ __  ___
   / /\/ '_ \| __/ _ \ '__| |_ / _` |/ __/ _ \  / _\| | | | '_ \ / __| __| |/ _ \| '_ \/ __|
/\/ /_ | | | | ||  __/ |  |  _| (_| | (_|  __/ / /  | |_| | | | | (__| |_| | (_) | | | \__ \
\____/ |_| |_|\__\___|_|  |_|  \__,_|\___\___| \/    \__,_|_| |_|\___|\__|_|\___/|_| |_|___/


*/

PriorityQueue priorityQueueCreate(CopyPriorityQueueElement copyElement,
								  FreePriorityQueueElement freeElement){
	NULL_CHECK(copyElement,NULL);
	NULL_CHECK(freeElement,NULL);
	PriorityQueue result=malloc(sizeof(*result));
	NULL_CHECK(result,NULL);
	result->first=NULL;
	result->copyFunction=copyElement;
	result->freeFunction=freeElement;
	return result;
}

void priorityQueueDestroy(PriorityQueue priorityQueue){
	priorityQueueClear(priorityQueue);
	free(priorityQueue);
}


PriorityQueue priorityQueueCopy(PriorityQueue priorityQueue){
	return priorityQueueFilter(priorityQueue,passEverything);
}


int priorityQueueGetSize(PriorityQueue priorityQueue){
	NULL_CHECK(priorityQueue,-1);
	int size=0;
	FOR_EACH (nodeTemp,priorityQueue){
		size++;
	}
	return size;
}


PriorityQueueElement priorityQueueTop(PriorityQueue priorityQueue){
	if (priorityQueueGetSize(priorityQueue)<1){
		return NULL;
	}
	return priorityQueue->first->data;
}


PriorityQueueResult priorityQueueAdd(PriorityQueue priorityQueue,
	PriorityQueueElement element, Priority priority){
	NULL_CHECK(priorityQueue,PRIORITY_QUEUE_NULL_ARGUMENT);
	NULL_CHECK(element,PRIORITY_QUEUE_NULL_ARGUMENT);
	Node newNode=malloc(sizeof(*newNode));
	NULL_CHECK(newNode,PRIORITY_QUEUE_OUT_OF_MEMORY);
	newNode->data=priorityQueue->copyFunction(element);
	if (newNode->data==NULL) {
		free(newNode);
		return PRIORITY_QUEUE_OUT_OF_MEMORY;
	}
	newNode->priority=priority;
	Node *current=&priorityQueue->first;
	//FOR_EACH loop doesn't make any sense here.
	while (*current && (*current)->priority>=priority) {
		current=&(*current)->next;
	}
	newNode->next=*current;
	*current=newNode;
	return PRIORITY_QUEUE_SUCCESS;
}


PriorityQueueResult priorityQueuePop(PriorityQueue priorityQueue){
	switch (priorityQueueGetSize(priorityQueue)){
		case -1:
			return PRIORITY_QUEUE_NULL_ARGUMENT;
		case 0:
			return PRIORITY_QUEUE_EMPTY;
	}
	Node nextNode=priorityQueue->first->next;
	priorityQueue->freeFunction(priorityQueue->first->data);
	free(priorityQueue->first);
	priorityQueue->first=nextNode;
	return PRIORITY_QUEUE_SUCCESS;
}


PriorityQueue priorityQueueFilter(PriorityQueue priorityQueue,
		FilterPriorityQueueFunction holdsCondition){
	NULL_CHECK(priorityQueue,NULL);
	NULL_CHECK(holdsCondition,NULL);
	PriorityQueue result=priorityQueueCreate(priorityQueue->copyFunction,
			priorityQueue->freeFunction);
	NULL_CHECK(result,NULL);
	FOR_EACH(node,priorityQueue) {
		if (!holdsCondition(node->data)) {
			continue;
		}
		if (priorityQueueAdd(result,node->data,node->priority)!=
				PRIORITY_QUEUE_SUCCESS) {
			priorityQueueDestroy(result);
			return NULL;
		}
	}
	return result;
}


PriorityQueueResult priorityQueueClear(PriorityQueue priorityQueue){
	NULL_CHECK(priorityQueue,PRIORITY_QUEUE_NULL_ARGUMENT);
	while(priorityQueuePop(priorityQueue)!=PRIORITY_QUEUE_EMPTY);
	return PRIORITY_QUEUE_SUCCESS;
}





